﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using MyPhotoIndex.Plugins.ImageFormatProviders;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.Utilities;
using System.Text.RegularExpressions;
using System.Threading;
using MyPhotoIndex.Utilities.WindowsManagement;

namespace MyPhotoIndex.ImageBank
{
    public class ImageChangeNotificationHandler : IDisposable
    {
        private Dictionary<string, FileSystemWatcher> m_directoryWatchers = new Dictionary<string, FileSystemWatcher>();
        private string[] m_watcherSupportedFileExtentions;
        private string m_watcherSupportedFileExtentionsString;
        private Queue<ImageCahngeNotificationPack> m_changesQueue = new Queue<ImageCahngeNotificationPack>();
        private Dictionary<int, DateTime> m_changeLog = new Dictionary<int, DateTime>();
        private Thread m_changeProcessingThread;
        private object m_threadSyncLock = new object();
        private object m_syncLock = new object();
        private DateTime m_lastfullScan = DateTime.MinValue;
        private DateTime m_lastWatchersReset = DateTime.MinValue;


        private EventHandler<DeviceNotifictionEventArgs> m_deviceInsertDelegate;
        private EventHandler<DeviceNotifictionEventArgs> m_deviceRemovedDelegate;
        private EventHandler<DeviceNotifictionEventArgs> m_CDInsertDelegate;
        private EventHandler<DeviceNotifictionEventArgs> m_CDRemovedDelegate;
        private EventHandler<DeviceNotifictionEventArgs> m_networkChangedDelegate;

        ~ImageChangeNotificationHandler()
        {
            Dispose(false);
        }

        public void Init()
        {
            m_deviceInsertDelegate = new EventHandler<DeviceNotifictionEventArgs>(RemovableDevice_DeviceInsertEvent);
            RemovableDevices.DeviceInsertEvent += m_deviceInsertDelegate;
            m_deviceRemovedDelegate = new EventHandler<DeviceNotifictionEventArgs>(RemovableDevice_DeviceRemovedEvent);
            RemovableDevices.DeviceRemovedEvent += m_deviceRemovedDelegate;
            m_CDInsertDelegate = new EventHandler<DeviceNotifictionEventArgs>(RemovableDevice_CDInsertEvent);
            RemovableDevices.CDInsertEvent += m_CDInsertDelegate;
            m_CDRemovedDelegate = new EventHandler<DeviceNotifictionEventArgs>(RemovableDevice_CDRemovedEvent);
            RemovableDevices.CDRemovedEvent += m_CDRemovedDelegate;
            m_networkChangedDelegate = new EventHandler<DeviceNotifictionEventArgs>(RemovableDevice_NetworkChangedEvent);
            RemovableDevices.NetworkStatusEvent += m_networkChangedDelegate;

            m_watcherSupportedFileExtentions = ImageFormatProviders.Default.SupportedImageExtentions;
            m_watcherSupportedFileExtentionsString = @"^.*\.(";
            for (int i = 0; i < m_watcherSupportedFileExtentions.Length; i++)
            {
                m_watcherSupportedFileExtentionsString += m_watcherSupportedFileExtentions[i];
                if (i - 1 < m_watcherSupportedFileExtentions.Length)
                {
                    m_watcherSupportedFileExtentionsString += "|";
                }
            }
            m_watcherSupportedFileExtentionsString += ")$";

            foreach (IImageData imageData in Storage.Instance.UnFiltered.Values)
            {
                WatchImage(imageData);
            }

            UpdateStorageImageFilesStatus();
        }

        public void ResetWatchers()
        {
            DateTime futeureTime = DateTime.Now.Add(new TimeSpan(0, 0, -10));
            if (m_lastWatchersReset.CompareTo(futeureTime) < 0)
            {
                m_lastWatchersReset = DateTime.Now;

                foreach (FileSystemWatcher watcher in m_directoryWatchers.Values)
                {
                    watcher.EnableRaisingEvents = false;
                    watcher.Dispose();
                }

                m_directoryWatchers.Clear();
                m_changeLog.Clear();

                m_watcherSupportedFileExtentions = ImageFormatProviders.Default.SupportedImageExtentions;
                m_watcherSupportedFileExtentionsString = @"^.*\.(";
                for (int i = 0; i < m_watcherSupportedFileExtentions.Length; i++)
                {
                    m_watcherSupportedFileExtentionsString += m_watcherSupportedFileExtentions[i];
                    if (i - 1 < m_watcherSupportedFileExtentions.Length)
                    {
                        m_watcherSupportedFileExtentionsString += "|";
                    }
                }
                m_watcherSupportedFileExtentionsString += ")$";

                foreach (IImageData imageData in Storage.Instance.UnFiltered.Values)
                {
                    WatchImage(imageData);
                }
            }
        }

        void RemovableDevice_CDRemovedEvent(object sender, DeviceNotifictionEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Drive) == true)
            {
                UpdateStorageImageFilesStatus();
            }
            else
            {
                //do partial update...
            }
        }

        void RemovableDevice_CDInsertEvent(object sender, DeviceNotifictionEventArgs e)
        {//Since CD/DVD media is usually Read-only we do'nt need to watch for changes
            if (string.IsNullOrEmpty(e.Drive) == true)
            {
                UpdateStorageImageFilesStatus();
            }
            else
            {
                //do partial update...
            }
        }

        void RemovableDevice_DeviceRemovedEvent(object sender, DeviceNotifictionEventArgs e)
        {
            // Since a potential Writable media has been inserted we need to update our monitors.
            ResetWatchers();
            UpdateStorageImageFilesStatus(new TimeSpan(0, 0, 15));
        }

        void RemovableDevice_DeviceInsertEvent(object sender, DeviceNotifictionEventArgs e)
        {
            // Since a potential Writable media has been removed we need to update our monitors.
            ResetWatchers();
            UpdateStorageImageFilesStatus(new TimeSpan(0, 0, 15));
        }

        void RemovableDevice_NetworkChangedEvent(object sender, DeviceNotifictionEventArgs e)
        {
            ResetWatchers();
            UpdateStorageImageFilesStatus(new TimeSpan(0, 0, 5));
        }

        public void WatchImage(IImageData imageData)
        {
            try
            {
                string basePath = Path.GetPathRoot(imageData.FileName);
                DriveInfo driveInfo = new DriveInfo(basePath);
                if (driveInfo != null)
                {
                    if (driveInfo.DriveType == DriveType.Fixed)
                    {
                        CreateFileWatcher(basePath);
                    }
                    else
                        if (driveInfo.DriveType == DriveType.Removable)
                        {
                            CreateFileWatcher(basePath);
                        }
                        else
                            if (driveInfo.DriveType == DriveType.Network)
                            {
                                CreateFileWatcher(basePath);
                            }
                }
                else
                {
                    imageData.LinkStatus = ImageLinkStatus.Invalid;
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        private void CreateFileWatcher(string basePath)
        {
            if (m_directoryWatchers.ContainsKey(basePath) == false)
            {
                FileSystemWatcher watcher = new FileSystemWatcher(basePath);
                watcher.IncludeSubdirectories = true;

                watcher.Changed += new FileSystemEventHandler(watcher_Changed);
                watcher.Created += new FileSystemEventHandler(watcher_Created);
                watcher.Deleted += new FileSystemEventHandler(watcher_Deleted);
                watcher.Error += new ErrorEventHandler(watcher_Error);
                watcher.EnableRaisingEvents = true;

                m_directoryWatchers.Add(basePath, watcher);
            }
        }

        void watcher_Error(object sender, ErrorEventArgs e)
        {
            Logger.Log(e.GetException());
        }

        void watcher_Deleted(object sender, FileSystemEventArgs e)
        {
            HandleFileCahngeEvent(e);
        }

        void watcher_Created(object sender, FileSystemEventArgs e)
        {
            HandleFileCahngeEvent(e);
        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            HandleFileCahngeEvent(e);
        }

        private void HandleFileCahngeEvent(FileSystemEventArgs e)
        {
            string fullPath = e.FullPath;
            string fileExtention = Path.GetExtension(fullPath);
            bool isMatch = Regex.IsMatch(fileExtention, m_watcherSupportedFileExtentionsString, RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
            if (isMatch == true)
            {
                IImageData changedImage = Storage.Instance.FindByPath(fullPath);
                if (changedImage != null)
                {
                    lock (m_syncLock)
                    {
                        DateTime lastCahnge = DateTime.MinValue;
                        if (m_changeLog.TryGetValue(changedImage.Id, out lastCahnge) == true)
                        {
                            if (lastCahnge.CompareTo(DateTime.Now.Add(new TimeSpan(0, 0, -5))) < 0)
                            {
                                m_changesQueue.Enqueue(new ImageCahngeNotificationPack(e, changedImage));
                                m_changeLog[changedImage.Id] = DateTime.Now;
                                InitChangeHandlerThread();
                            }
                        }
                        else
                        {
                            m_changesQueue.Enqueue(new ImageCahngeNotificationPack(e, changedImage));
                            m_changeLog.Add(changedImage.Id, DateTime.Now);
                            InitChangeHandlerThread();
                        }
                    }
                }
            }
        }

        private void InitChangeHandlerThread()
        {
            if (m_changeProcessingThread == null)
            {
                lock (m_threadSyncLock)
                {
                    if (m_changeProcessingThread == null)
                    {
                        m_changeProcessingThread = new Thread(new ThreadStart(ProcessChanges));
                        m_changeProcessingThread.Start();
                    }
                }
            }
        }

        private void ProcessChanges()
        {
            Thread.Sleep(1000);//some grace period to let the file updaters to finish.

            while (m_changesQueue.Count > 0)
            {
                ImageCahngeNotificationPack pack = m_changesQueue.Dequeue();
                if (pack != null)
                {
                    IImageData changedImage = pack.ImageData;
                    if ((pack.Event.ChangeType & WatcherChangeTypes.Changed) == WatcherChangeTypes.Changed)
                    {
                        DateTime lastChanged = changedImage.FormatProvider.GetImageMediaLastUpdated(changedImage.FileName);

                        changedImage.LastModified = lastChanged;
                        changedImage.Update(false, true);
                    }
                }

                Thread.Sleep(20);
            }


            lock (m_threadSyncLock)
            {
                m_changeProcessingThread = null;
            }
        }

        public void UpdateStorageImageFilesStatus()
        {
            UpdateStorageImageFilesStatus(TimeSpan.MinValue);
        }

        public void UpdateStorageImageFilesStatus(TimeSpan delay)
        {
            DateTime futeureTime = DateTime.Now.Add(new TimeSpan(0, 0, -10));
            if (m_lastfullScan.CompareTo(futeureTime) < 0)
            {
                m_lastfullScan = DateTime.Now;
                ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateStorageImageFilesStatusAsync), delay);
            }
        }

        private void UpdateStorageImageFilesStatusAsync(object state)
        {
            try
            {
                TimeSpan delay = (TimeSpan)state;
                if (delay != TimeSpan.MinValue)
                {
                    Thread.Sleep((int)delay.TotalMilliseconds);
                }

                List<IImageData> list = null;
                lock (Storage.Instance.UnFiltered)
                {
                    list = new List<IImageData>(Storage.Instance.UnFiltered.Values);
                }

                foreach (IImageData imageData in list)
                {
                    try
                    {
                        lock (imageData)
                        {
                            bool fileExists = imageData.FormatProvider.Exists(imageData.FileName);

                            if (fileExists == true &&
                                imageData.LinkStatus != ImageLinkStatus.Invalid)
                            {
                                DateTime lastChanged = imageData.FormatProvider.GetImageMediaLastUpdated(imageData.FileName);

                                if (imageData.LastModified.Year != lastChanged.Year ||
                                    imageData.LastModified.Month != lastChanged.Month ||
                                    imageData.LastModified.Day != lastChanged.Day ||
                                    imageData.LastModified.Hour != lastChanged.Hour ||
                                    imageData.LastModified.Minute != lastChanged.Minute ||
                                    imageData.LastModified.Second != lastChanged.Second)
                                {
                                    imageData.LastModified = lastChanged;
                                    imageData.Update(false, true);
                                    Thread.Sleep(20);
                                }
                            }
                            else
                                if (fileExists == false &&
                                   imageData.LinkStatus != ImageLinkStatus.Invalid)
                                {
                                    imageData.LinkStatus = ImageLinkStatus.Invalid;
                                    imageData.InvokeChangedEvent();
                                }
                                else
                                    if (fileExists == true &&
                                        imageData.LinkStatus == ImageLinkStatus.Invalid)
                                    {
                                        imageData.LinkStatus = ImageLinkStatus.Valid;
                                        imageData.InvokeChangedEvent();
                                    }
                        }
                    }
                    catch (FileNotFoundException ex)
                    {
                        Logger.Log(ex);
                        imageData.LinkStatus = ImageLinkStatus.Invalid;
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(ex);
                    }

                    if (Storage.Instance.ShuttingDown == true)
                    {
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool dispose)
        {
            if (dispose)
            {
                foreach (FileSystemWatcher watcher in m_directoryWatchers.Values)
                {
                    watcher.EnableRaisingEvents = false;
                    watcher.Dispose();
                }

                RemovableDevices.DeviceInsertEvent -= m_deviceInsertDelegate;
                RemovableDevices.DeviceRemovedEvent -= m_deviceRemovedDelegate;
                RemovableDevices.CDInsertEvent -= m_CDInsertDelegate;
                RemovableDevices.CDRemovedEvent -= m_CDRemovedDelegate;
                RemovableDevices.NetworkStatusEvent -= m_networkChangedDelegate;
            }
        }

        #endregion
    }

}
